import org.sonatype.nexus.repository.storage.Asset
import org.sonatype.nexus.repository.storage.Component
import org.sonatype.nexus.repository.storage.Query
import org.sonatype.nexus.repository.storage.StorageFacet

import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import org.sonatype.nexus.repository.Repository

class RepositoryProcessor {
    private final log
    private final repository
    private final String repoName = 'my-repository'
    private final String[] ignoreVersions = ['build-latest']
    private final int processIfSizeGt = 3
    private final int delAllButMostRecentNImages = 2

    RepositoryProcessor(log, repository) {
        this.log = log
        this.repository = repository
    }

    void processRepository() {
        def repo = repository.repositoryManager.get(repoName)
        log.debug("found repository: {}", repo)
        // will use default of sonatype
        // https://github.com/sonatype/nexus-public/blob/master/components/nexus-repository/src/main/java/org/sonatype/nexus/repository/storage/StorageFacetImpl.java
        StorageFacet storageFacet = repo.facet(StorageFacet)
        log.debug("initiated storage facet: {}", storageFacet.toString())
        // tx of type https://github.com/sonatype/nexus-public/blob/master/components/nexus-repository/src/main/java/org/sonatype/nexus/repository/storage/StorageTxImpl.java $$EnhancerByGuice ??
        def transaction = storageFacet.txSupplier().get()
        log.debug("initiated transaction instance: {}", transaction.toString())

        try {
            transaction.begin()

            log.info("asset count {}", transaction.countAssets(Query.builder().build(), [repo]))
            log.info("components count {}", transaction.countComponents(Query.builder().build(), [repo]))

            // queried db is orientdb, syntax is adapted to it
            def components = transaction.findComponents(Query.builder()
            // .where("NOT (name LIKE '%service-A%')")
            // .and("NOT (name LIKE '%service-B%')")
                    .build(), [repo])
            // cp and cpt refers to component
            // group by name eg: repository/my-repository/some-project/service-A
            def groupedCps = components.groupBy{ it.name() }.collect()

            // fetch assets for each cp
            // and set them in maps to delete the old ones
            groupedCps.each{ cpEntry ->
                // add
                cpEntry = (Map.Entry<String, List<Component>>) cpEntry

                // process only if its greater than the minimum amount of images per service
                if (cpEntry.value.size() > processIfSizeGt) {
                    // single component processing (i.e this would be done for each service)
                    def cpMap = [:] // map with key eq id
                    def cpAssetsMap = [:] // map of cp assets where key eq cp id
                    // process service cpts
                    cpEntry.value.each { cp ->
                        // cp id of type https://github.com/sonatype/nexus-public/blob/master/components/nexus-orient/src/main/java/org/sonatype/nexus/orient/entity/AttachedEntityId.java
                        def cpId = cp.entityMetadata.id.identity
                        // asset of type: https://github.com/sonatype/nexus-public/blob/master/components/nexus-repository/src/main/java/org/sonatype/nexus/repository/storage/Asset.java
                        def cpAssets = transaction.browseAssets(cp).collect()

                        // document of type https://github.com/joansmith1/orientdb/blob/master/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocument.java
                        // _fields of type: https://github.com/joansmith1/orientdb/blob/master/core/src/main/java/com/orientechnologies/orient/core/record/impl/ODocumentEntry.java
                        // any field is of type ODocumentEntry.java
                        // append to map if it does not belong to the ignored versions
                        if (!(cp.entityMetadata.document._fields.version.value in ignoreVersions)) {
                            cpMap.put(cpId, cp)
                            cpAssetsMap.put(cpId, cpAssets)
                        }
                    }
                    // log info about the affected folder/service
                    log.info("cp map size: {}, versions: {}",
                            cpMap.values().size(),
                            cpMap.values().entityMetadata.document._fields.version.value)
                    // order desc by last_downloaded (default is asc)
                    log.debug("cp map assets of size: {}", cpAssetsMap.values().size())
                    def sortedFilteredList = cpAssetsMap.values()
                            .sort { it.entityMetadata.document._fields.last_downloaded?.value }
                            .reverse(true)
                            .drop(delAllButMostRecentNImages)
                    // list of cp ids from the assets that going to be deleted
                    def sortedAssetsCps = sortedFilteredList.entityMetadata.document._fields.component?.value?.flatten()
                    log.info("cp map assets size after filtering {}", sortedFilteredList.size())
                    // this will print the cps ids to delete
                    log.debug("elements to delete : sorted assets cps list {}", sortedAssetsCps)
                    // deleting components and their assets
                    cpMap.findAll { it.key in sortedAssetsCps }
                            .each { entry ->
                                log.info("deleting cp version {}", entry.value.entityMetadata.document._fields.version?.value)
                                // this will call delete asset internally, and by default will delete blob
                                transaction.deleteComponent(entry.value)
                            }
                }
            }
            transaction.commit();
        } catch (Exception e) {
            log.warn("transaction failed {}", e.toString())
            transaction.rollback()
        } finally {
            transaction.close();
        }
    }
}

new RepositoryProcessor(log, repository).processRepository()
